#!/usr/bin/python

import re
import zutil

#update parser.cc

# string storing the interface section of the parser.cc file
# NB: this is GLOBAL and should not be moved.
s_file_interface = ""

def get_func_info(s_func_name):
    """return a tuple of:
    - s_full - the function declaration
    - s_full_curlies - function declaration plus body
    - s_call - function in format of being called
    """
    global s_file_interface
    
    # find function declaration 

    p = re.compile(r"\w+[*]?\s+" + s_func_name + r"[(].*?[)]", re.DOTALL)
    m = p.search(s_file_interface)
    if m == None: raise Exception("zbuild_parser.py: Function " + s_func_name + " not found")
    s_full = m.group()
    pos_func = m.start()


    # find the function body enclosed in { }

    p = re.compile(r"{|}")
    m = p.search(s_file_interface, pos_func)
    pos_curly = pos_func
    num_curlies = 0

    while True :
        m = p.search(s_file_interface, pos_curly)
        s_curly = m.group()
        pos_curly = m.start()
        if s_curly == "{": num_curlies += 1
        else : num_curlies -= 1
    
        pos_curly += 1 # increment before break as slice does not include ending pos
        if num_curlies == 0: break

    s_full_curlies = s_file_interface[pos_func:pos_curly]


    # create a function call
    
    # a) strip return value
    p_call = re.compile(r"\w+[*]?\s+(.+)", re.DOTALL)
    m_call = p_call.search(s_full)
    #print m_call.group()
    #print m_call.group(1) # the capturing group captures the func name and parens
    s_call = m_call.group(1)

    # b) strip the types declarations between the parens
    #p_call = re.compile(r"(\w+[*]?)\s+(\w+)", re.DOTALL)
    p_call = re.compile(r"(\w+(?:\<\w+[*]?\>)?[*]?)\s+(\w+)", re.DOTALL)
    #m_call = p_call.search(s_call)
    #print m_call.group()
    #print m_call.group(1)
    s_call = p_call.sub(r"\2", s_call)
    # get rid of the namespace ::
    s_call = re.sub("(\w+::)", "",s_call)
    #print s_call
    
    
    # return the values as a tuple
    return (s_full, s_full_curlies, s_call)
    

# generate new function which will calls either the js or zs functions
def new_func_generic(s_func_name, s_call, s_full):
    s_template = \
r""" {
    if (is_zed(#param#))
        return #zs#;
    else 
        return #js#;
}
"""
    # cutomise is_zed param for PreParse
    #s_param = "script"
    #if s_func_name == "PreParse": s_param = "stream"
    s_param = "stream" if s_func_name == "PreParse" else "script"
    s_template = s_template.replace("#param#", s_param)

    # process the template
    s_call_js = s_call.replace(s_func_name, s_func_name + "js")
    s_call_zs = s_call.replace(s_func_name, s_func_name + "zs")
    #print s_call_zs
    s_template = s_template.replace("#zs#", s_call_zs)
    s_template = s_template.replace("#js#", s_call_js)
    #print s_template
    s_return = s_full + s_template
    #print s_return
    return s_return
    

def new_func_zs(s_func_name, s_full_curlies):
    """generate new zs function from a copy of the original function"""
    s_return = s_full_curlies.replace(s_func_name, s_func_name + "zs", 1) # only replace 1st match
    s_return = s_return.replace("AstBuildingParser", "ZAstBuildingParser")
    # print s_return
    return s_return


def process_func(s_func_name):
    """for func_name do processing"""
    
    (s_full, s_full_curlies, s_call) = get_func_info(s_func_name)
  
    # create and return a string with 2 new funcs 
    s_inject = ""
    s_inject += new_func_zs(s_func_name, s_full_curlies)
    s_inject += "\n\n\n"
    s_inject += new_func_generic(s_func_name, s_call, s_full)
    s_inject += "\n\n\n"

    return s_inject


def process_func_js_rename(s_func_name):
    """find the func name in the original file and append js"""
    global s_file_interface
    
    (s_full, s_full_curlies, s_call) = get_func_info(s_func_name)
    
    s_js = s_full.replace(s_func_name, s_func_name + "js")
    s_file_interface = s_file_interface.replace(s_full, s_js)
    

def virtualize(s_file):
    """ Add virtual prefix to methods in Parser class interface"""

    def virtual(m):
        s = m.group()
        #print s
        s_begin = s.lstrip()
        
        not_virtual = ["MakeCatchContext"]
        if not s_begin.startswith(("virtual", "//", "Parser(")) and \
        not any(s.find(nv) != -1 for nv in not_virtual):
            s = "  virtual " + s.lstrip()
        return s
    
    # load the Parser class interface into the string 's_class_interface'
    p = re.compile(r"class Parser {.*friend class TemporaryScope", re.DOTALL);
    m = p.search(s_file)
    if m == None: raise Exception("class interface in parser.cc not found")
    s_class_interface = m.group()
    #print(s_class_interface)
    #print("**********************************")
    
    # prefix virtual to methods which are not:
    # - prefixed wit 'virtual' already
    # - prefixed with // for comments
    # - are not methods - ie do not contain (
    pv = re.compile(r"^.+?\(.*?$", re.MULTILINE);
    s_class_interface = pv.sub(virtual, s_class_interface)
    #print s_class_interface
 
    # Update s_file with revised interface
    s_file = p.sub(s_class_interface, s_file)
    
    return s_file

    
def tweak_PreParsezs():
    """ TODO: PreParse not implemented for zed YET."""
    global s_file_interface
    
    (s_full, s_full_curlies, s_call) = get_func_info("PreParsezs")
    
    s_new = s_full + " {\n  return NULL;\n}"
    
    s_file_interface = s_file_interface.replace(s_full_curlies, s_new, 1)


def main():
    global s_file_interface
    
    # read the file
    f = open(zutil.get_read_path("parser.cc"), "r")
    s_file = f.read()
    f.close()
    
    # get the interface section
    p = re.compile(r"// The Parser interface\..*#undef NEW", re.DOTALL)
    m = p.search(s_file)
    if m == None: raise Exception("Parser interface in parser.cc not found")
    s_file_interface = m.group()
    #print(s_file_interface)
    
    # 1. generate string of code to inject e.g 'new generic' MakeAST and MakeASTzs
    # 2. update original function name in the file e.g MakeAST to MakeASTjs
    s_inject = ""
    funcs = ["MakeAST", "MakeLazyAST", "PreParse"]
    for fun in funcs:
        s_inject += process_func(fun)
        process_func_js_rename(fun) # updates s_file_interface - no return value
    #print s_inject
    
    # inject the zed code
    inject_where = "#undef NEW"
    inject_include = """// Include Zed parser subclass\n#include "zparser.cc"\n """
    s_file_interface = s_file_interface.replace(inject_where,
                                                r"// Z: START\n\n" + inject_include + 
                                                r"\n\n // --- Z: Parser interface ---\n\n" + s_inject + 
                                                r"\n// Z: END\n\n" + inject_where)
    
    # no preparsing for zed - tweak the PreParsezs method in s_file_interface to return NULL
    tweak_PreParsezs()
    
    # Update s_file with revised parser interface
    s_file = p.sub(s_file_interface, s_file)
    
    # update Parser class with virtual methods
    s_file = virtualize(s_file)

    
    # write the file
    f = open(zutil.get_write_path("parser.cc"), "w")
    f.write(s_file)
    f.close()


# call the main function
if __name__ == "__main__":
    main()





